#!/bin/sh

# Copyright (C) 2016  Desktopd Developers.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


cd "`dirname "$0"`"

cloudflareRulesLimit=200

configPath="./config"
[ -f "$configPath" ] || {
	printf "No such configuration file: %s\n" "$configPath"
	exit 1
}

. "$configPath"

[ "$cloudflareAuthEmail" ] && [ "$cloudflareAuthKey" ] || {
	printf "Invalid configuration file: %s\n" "$configPath"
	exit 1
}


dataDir="./data"
exitListPath="${dataDir}/exit-list"
exitListCachePath="${dataDir}/exit-list.cache"
whitelistDBDir="${dataDir}/whitelist-db"

version='1.0'
faqURI='https://www.torproject.org/docs/faq-abuse.html.en'
curlUserAgent="Mozilla/5.0 (compatible; ExitWhitelister/${version}; +${faqURI})"

exitListURI='https://check.torproject.org/exit-addresses'
onionooEndpointAPI='https://onionoo.torproject.org'
cloudflareEndpointAPI='https://api.cloudflare.com/client/v4'


mkdir -p "$dataDir"
mkdir -p "$whitelistDBDir"

getExitList () {
	torsocks -i curl --retry 5 -A '' "$exitListURI" \
	| grep 'ExitAddress' | awk '{print $2}' | sort -V | uniq
}

getExitListOnionoo () {
	torsocks -i curl -A '' --retry 5 \
		"${onionooEndpointAPI}/details" \
		-G -d running=True -d flag=Exit \
		-d fields=exit_probability,or_addresses -d order=-consensus_weight \
		-d limit="$cloudflareRulesLimit" \
	| grep '"or_addresses"' \
	| sed 's|^.*"or_addresses":\["\([^"]*\)".*$|\1| ; s|:.*||'
}

# Single IP address -> whitelist ID
createWhitelistID () {
	{
		# POST
		curl \
			-A "$curlUserAgent" \
			-H "X-Auth-Email: ${cloudflareAuthEmail}" \
			-H "X-Auth-Key: ${cloudflareAuthKey}" \
			-H "Content-Type: application/json" \
			-H "Accept: application/json" \
			--data-binary '@-' \
			"${cloudflareEndpointAPI}/user/firewall/access_rules/rules" <<JSON
{"mode":"whitelist"
,"notes":"Exit whitelist: automatically managed"
,"configuration":
	{"target":"ip"
	,"value":"${1}"}}
JSON
	} \
	| sed 's/,/,\n/' | grep '"id":' | sed 's/^.*"id":"\([^"]*\)".*$/\1/' \
	| head -n 1
	
	# Note: This is a quick 'n dirty attempt to JSON 'parsing'
	# TODO: It may be better to write a generic JSON parser even in shell script
}

# Whitelist ID
removeWhitelistById () {
	# DELETE
	curl \
		-A "$curlUserAgent" \
		-H "X-Auth-Email: ${cloudflareAuthEmail}" \
		-H "X-Auth-Key: ${cloudflareAuthKey}" \
		-X DELETE \
		"${cloudflareEndpointAPI}/user/firewall/access_rules/rules/${1}"
}

# Single IP address
addWhitelist () {
	id="`createWhitelistID "${1}"`"
	[ "$id" ] || return 1
	echo "$id" > "${whitelistDBDir}/${1}"
}

# Single IP address
removeWhitelist () {
	id="`cat "${whitelistDBDir}/${1}" 2>/dev/null`"
	[ "$id" ] || return 1
	removeWhitelistById "$id" && shred -uz "${whitelistDBDir}/${1}"
}


getExitListOnionoo > "$exitListPath"
[ -f "$exitListCachePath" ] || touch "$exitListCachePath"

for obsoleteIP in `\
	diff "$exitListCachePath" "$exitListPath" \
	| grep '^<' | sed 's/^<\s*//' `
do [ "$obsoleteIP" ] || continue
	printf "[-] Removing an IP from the whitelist:\t%s\n" "$obsoleteIP"
	removeWhitelist "$obsoleteIP" || {
		printf "\n[!] Failed while removing:\t%s\n" "$obsoleteIP"
	}
	sleep 1
done

for newIP in `\
	diff "$exitListCachePath" "$exitListPath" \
	| grep '^>' | sed 's/^>\s*//' `
do [ "$newIP" ] || continue
	printf "[+] Adding a new IP to the whitelist:\t%s\n" "$newIP"
	addWhitelist "$newIP" || {
		printf "\n[!] Failed while adding:\t%s\n" "$newIP"
	}
	sleep 1
done

#exit
cat "$exitListPath" > "$exitListCachePath"

# vim: set ts=4 noet ai
